#ifdef __EOS__
#include <rtthread.h>
#include "irq_numbers.h"
#include "clk.h"
#else
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/printk.h>
#include <linux/version.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#endif

#include "ei_type.h"
#include "ei_common.h"
#include "ei_os.h"
#include "ei_comm_isp.h"
#include "ei_comm_ippu.h"

extern ISP_RESOURCE_S g_astIspRes[ISP_HW_NUM];
extern ISP_DEV g_IspDevId;

extern int ISP_ModInit(void);
extern void ISP_ModExit(void);
extern void ISP_DRV_HandleIprocIsr(EI_U32 dev);
extern void ISP_DRV_HandleCoreIsr(EI_U32 dev);
extern void IPPU_DRV_HandleChnIsr(EI_U32 dev);

static int isp_iproc_irq_handler(int irq, void *priv)
{
    ISP_RESOURCE_S *pstIspRes = priv;
    EI_U32 dev = (pstIspRes->u32IspDev == 0) ? g_IspDevId : pstIspRes->u32IspDev;

    ISP_DRV_HandleIprocIsr(dev);

    return OSAL_IRQ_HANDLED;
}

static int isp_core_irq_handler(int irq, void *priv)
{
    ISP_RESOURCE_S *pstIspRes = priv;
    EI_U32 dev = (pstIspRes->u32IspDev == 0) ? g_IspDevId : pstIspRes->u32IspDev;

    ISP_DRV_HandleCoreIsr(dev);

    return OSAL_IRQ_HANDLED;
}

static int ippu_chn_irq_handler(int irq, void *priv)
{
    ISP_RESOURCE_S *pstIspRes = priv;
    EI_U32 dev = (pstIspRes->u32IspDev == 0) ? g_IspDevId : pstIspRes->u32IspDev;

    IPPU_DRV_HandleChnIsr(dev);

    return OSAL_IRQ_HANDLED;
}

#ifdef __EOS__

extern char *ISP_TOOL_GetPathName(EI_U32 dev);
extern char *ISP_TOOL_GetSensorName(EI_U32 dev);
extern void ISP_TOOL_GetJsonName(EI_U32 dev, EI_CHAR *pcJsonName);
extern void ISP_TOOL_GetChnWH(EI_U32 dev, EI_U32 ch, EI_S32 *W, EI_S32 *H);
extern void ISP_TOOL_GetChnFrame(EI_U32 dev, EI_U32 ch, VIDEO_FRAME_INFO_S *pstFrameInfo);
extern void ISP_TOOL_PutChnFrame(EI_U32 dev, EI_U32 ch, VIDEO_FRAME_INFO_S *pstFrameInfo);

extern void ISP_TOOL_DumpYuvPgm(EI_U32 dev, EI_U32 ch);
extern void ISP_TOOL_DumpRawPgm(EI_U32 dev);
extern void ISP_TOOL_DumpHeader(EI_U32 dev);

extern void ISP_TOOL_ReLoadJson(EI_U32 dev);

extern void ISP_TOOL_SensorWrite(EI_U32 dev, EI_U32 reg, EI_U32 val);
extern void ISP_TOOL_SensorRead(EI_U32 dev, EI_U32 reg);

struct isp_usb_info {
    char sensor_name[32];
    int width;
    int height;
    int json_len;
    union {
        int res[4096];
        struct {
            char json_name[64];
            int raw_pgm_len;
            int yuv_pgm_len;
            int header_len;
        } r;
    };
};

struct isp_usb_cmd {
    u8 cmd;         /* command 0xf6 */
    u8 flag;        /* modules */
    u32 addr;       /* address */
    u32 len;        /* len */
    u8 res[6];
} __packed;

#define ISP_USB_CMD_GET_IMAGE           (0x01)
#define ISP_USB_CMD_GET_JSON            (0x02)
#define ISP_USB_CMD_GET_SENSOR_INFO     (0x03)
#define ISP_USB_CMD_GET_SENSOR_REG      (0x04)
#define ISP_USB_CMD_GET_YUV_PGM_LEN     (0x05)
#define ISP_USB_CMD_GET_RAW_PGM_LEN     (0x06)
#define ISP_USB_CMD_GET_YUV_PGM         (0x07)
#define ISP_USB_CMD_GET_RAW_PGM         (0x08)
#define ISP_USB_CMD_GET_HEADER_LEN      (0x09)
#define ISP_USB_CMD_GET_HEADER          (0x0A)

#define ISP_USB_CMD_SET_SENSOR_REG      (0x40)
#define ISP_USB_CMD_SET_JSON            (0x80)
#define ISP_USB_CMD_SET_RELOAD          (0x81)
#define ISP_USB_CMD_SET_JSON_RELOAD     (0x82)

int isp_usb_read(void *cmd, void *data)
{
    struct isp_usb_cmd *c = (struct isp_usb_cmd *)cmd;
    char file_path[64] = {0};
    char json_name[64] = {0};
    EI_U32 dev = c->res[0];
    EI_U32 ch = c->res[2];

    ISP_CHECK_DEV(dev);
    IPPU_CHECK_PHY_CHN(ch);

    if (c->res[1] == ISP_USB_CMD_GET_IMAGE) {
        static VIDEO_FRAME_INFO_S stFrameInfo = {0};
        static int finished = 0;
        static int y = 0, uv = 0;
        static int w = 0, h = 0;

        if (!finished) {
            ISP_TOOL_GetChnWH(dev, ch, &w, &h);
            ISP_TOOL_GetChnFrame(dev, ch, &stFrameInfo);
            y = stFrameInfo.stVFrame.ulPlaneVirAddr[0];
            uv = stFrameInfo.stVFrame.ulPlaneVirAddr[1];
            finished = 1;
        }

        if (y == 0) {
            os_printk("frame buffer is NULL, read image failed!\n");
            return EI_FAILURE;
        }
        int diff = w * h - c->addr;
        /* format nv12 */
        if (diff >= (int)c->len) {
            os_memcpy(data, (void *)(y + c->addr), c->len);
        } else if (diff > 0) {
            os_memcpy(data, (void *)(y + c->addr), diff);
            os_memcpy(data+diff, (void *)(uv), c->len - diff);
        } else {
            os_memcpy(data, (void *)(uv - diff), c->len);
            if ((c->len - diff) == (w * h / 2)) {
                finished = 0;
                ISP_TOOL_PutChnFrame(dev, ch, &stFrameInfo);
                memset(&stFrameInfo, 0, sizeof(stFrameInfo));
                y = 0;
                uv = 0;
            }
        }
    } else if (c->res[1] == ISP_USB_CMD_GET_JSON) {
        static char *json = NULL;
        static int finished = 0;
        static int json_len = 0;

        if (!finished) {
            int fd, ret;
            struct stat st;

            ISP_TOOL_GetJsonName(dev, json_name);

            os_sprintf(file_path, "%s/%s.json", ISP_TOOL_GetPathName(dev), json_name);
            ret = stat(file_path, &st);
            if (ret) {
                os_sprintf(file_path, "/etc/%s.json", json_name);
                ret = stat(file_path, &st);
                if (ret) {
                    os_printk("stat %s error!\n", file_path);
                    return ret;
                }
            }
            json = os_kmalloc(st.st_size, os_gfp_kernel);
            if (json == NULL) {
                os_printk("os_kmalloc json mem failed!\n");
                return -EI_ERR_ISP_NOMEM;
            }

            fd = open(file_path, O_RDONLY, 0644);
            if (fd < 0) {
                os_printk("open %s failed!\n", file_path);
                return EI_FAILURE;
            }
            ret = read(fd, json, st.st_size);
            if (ret < 0) {
                os_printk("read %s error!\n", file_path);
                close(fd);
                return ret;
            }
            close(fd);

            finished = 1;
            json_len = st.st_size;
        }

        if (json) {
            os_memcpy(data, (void *)(json + c->addr), c->len);
            if ((c->len + c->addr) == json_len) {
                finished = 0;
                os_kfree(json);
                json = NULL;
            }
        }
    } else if (c->res[1] == ISP_USB_CMD_GET_SENSOR_INFO) {
        struct isp_usb_info *info = data;
        struct stat st;
        int ret;

        ISP_TOOL_GetJsonName(dev, json_name);

        os_sprintf(file_path, "%s/%s.json", ISP_TOOL_GetPathName(dev), json_name);
        ret = stat(file_path, &st);
        if (ret) {
            os_sprintf(file_path, "/etc/%s.json", json_name);
            stat(file_path, &st);
        }

        os_memcpy(info->sensor_name, ISP_TOOL_GetSensorName(dev), 32);
        os_memcpy(info->r.json_name, json_name, 64);
        ISP_TOOL_GetChnWH(dev, ch, &info->width, &info->height);
        info->json_len = st.st_size;
    } else if (c->res[1] == ISP_USB_CMD_GET_YUV_PGM_LEN) {
        struct isp_usb_info *info = data;
        struct stat st;
        int ret;

        ISP_TOOL_DumpYuvPgm(dev, ch);

        os_sprintf(file_path, "%s/isp%d_ch%d.pgm", ISP_TOOL_GetPathName(dev), dev, ch);
        ret = stat(file_path, &st);
        if (ret) {
            info->r.yuv_pgm_len = 0;
        } else {
            info->r.yuv_pgm_len = st.st_size;
        }
    } else if (c->res[1] == ISP_USB_CMD_GET_RAW_PGM_LEN) {
        struct isp_usb_info *info = data;
        struct stat st;
        int ret;

        ISP_TOOL_DumpRawPgm(dev);

        os_sprintf(file_path, "%s/isp%d_raw.pgm", ISP_TOOL_GetPathName(dev), dev);
        ret = stat(file_path, &st);
        if (ret) {
            info->r.raw_pgm_len = 0;
        } else {
            info->r.raw_pgm_len = st.st_size;
        }
    } else if (c->res[1] == ISP_USB_CMD_GET_HEADER_LEN) {
        struct isp_usb_info *info = data;
        struct stat st;
        int ret;

        ISP_TOOL_DumpHeader(dev);

        os_sprintf(file_path, "%s/isp%d_header.json", ISP_TOOL_GetPathName(dev), dev);
        ret = stat(file_path, &st);
        if (ret) {
            info->r.header_len = 0;
        } else {
            info->r.header_len = st.st_size;
        }
    } else if (c->res[1] == ISP_USB_CMD_GET_YUV_PGM) {
        static char *yuv_pgm = NULL;
        static int finished = 0;
        static int pgm_size = 0;

        if (!finished) {
            int fd, ret;
            struct stat st;

            os_sprintf(file_path, "%s/isp%d_ch%d.pgm", ISP_TOOL_GetPathName(dev), dev, ch);
            ret = stat(file_path, &st);
            if (ret) {
                os_printk("stat %s error!\n", file_path);
                return ret;
            }
            yuv_pgm = os_kmalloc(st.st_size, os_gfp_kernel);
            if (yuv_pgm == NULL) {
                os_printk("os_kmalloc yuv_pgm mem failed!\n");
                return -EI_ERR_ISP_NOMEM;
            }

            fd = open(file_path, O_RDONLY, 0644);
            if (fd < 0) {
                os_printk("open %s failed!\n", file_path);
                return EI_FAILURE;
            }
            ret = read(fd, yuv_pgm, st.st_size);
            if (ret < 0) {
                os_printk("read %s error!\n", file_path);
                close(fd);
                return ret;
            }
            close(fd);

            finished = 1;
            pgm_size = st.st_size;
        }

        if (yuv_pgm) {
            os_memcpy(data, (void *)(yuv_pgm + c->addr), c->len);
            if ((c->len + c->addr) == pgm_size) {
                finished = 0;
                os_kfree(yuv_pgm);
                yuv_pgm = NULL;
            }
        }
    } else if (c->res[1] == ISP_USB_CMD_GET_RAW_PGM) {
        static char *raw_pgm = NULL;
        static int finished = 0;
        static int pgm_size = 0;

        if (!finished) {
            int fd, ret;
            struct stat st;

            os_sprintf(file_path, "%s/isp%d_raw.pgm", ISP_TOOL_GetPathName(dev), dev);
            ret = stat(file_path, &st);
            if (ret) {
                os_printk("stat %s error!\n", file_path);
                return ret;
            }
            raw_pgm = os_kmalloc(st.st_size, os_gfp_kernel);
            if (raw_pgm == NULL) {
                os_printk("os_kmalloc raw_pgm mem failed!\n");
                return -EI_ERR_ISP_NOMEM;
            }

            fd = open(file_path, O_RDONLY, 0644);
            if (fd < 0) {
                os_printk("open %s failed!\n", file_path);
                return EI_FAILURE;
            }
            ret = read(fd, raw_pgm, st.st_size);
            if (ret < 0) {
                os_printk("read %s error!\n", file_path);
                close(fd);
                return ret;
            }
            close(fd);

            finished = 1;
            pgm_size = st.st_size;
        }

        if (raw_pgm) {
            os_memcpy(data, (void *)(raw_pgm + c->addr), c->len);
            if ((c->len + c->addr) == pgm_size) {
                finished = 0;
                os_kfree(raw_pgm);
                raw_pgm = NULL;
            }
        }
    } else if (c->res[1] == ISP_USB_CMD_GET_HEADER) {
            static char *header = NULL;
            static int finished = 0;
            static int header_len = 0;

            if (!finished) {
                int fd, ret;
                struct stat st;

                os_sprintf(file_path, "%s/isp%d_header.json", ISP_TOOL_GetPathName(dev), dev);
                ret = stat(file_path, &st);
                if (ret) {
                    os_printk("stat %s error!\n", file_path);
                    return ret;
                }
                header = os_kmalloc(st.st_size, os_gfp_kernel);
                if (header == NULL) {
                    os_printk("os_kmalloc header mem failed!\n");
                    return -EI_ERR_ISP_NOMEM;
                }

                fd = open(file_path, O_RDONLY, 0644);
                if (fd < 0) {
                    os_printk("open %s failed!\n", file_path);
                    return EI_FAILURE;
                }
                ret = read(fd, header, st.st_size);
                if (ret < 0) {
                    os_printk("read %s error!\n", file_path);
                    close(fd);
                    return ret;
                }
                close(fd);

                finished = 1;
                header_len = st.st_size;
            }

            if (header) {
                os_memcpy(data, (void *)(header + c->addr), c->len);
                if ((c->len + c->addr) == header_len) {
                    finished = 0;
                    os_kfree(header);
                    header = NULL;
                }
            }
    } else if (c->res[1] == ISP_USB_CMD_GET_SENSOR_REG) {
        os_printk("ISP_USB_CMD_GET_SENSOR_REG\n");
        ISP_TOOL_SensorRead(dev, c->addr);
    }

    return EI_SUCCESS;
}
RTM_EXPORT(isp_usb_read);

int isp_usb_write(void *cmd, void *data)
{
    struct isp_usb_cmd *c = (struct isp_usb_cmd *)cmd;
    char file_path[64] = {0};
    char json_name[64] = {0};
    EI_U32 dev = c->res[0];

    ISP_CHECK_DEV(dev);

    if ((c->res[1] == ISP_USB_CMD_SET_JSON) || (c->res[1] == ISP_USB_CMD_SET_JSON_RELOAD)) {
        static char *json = NULL;
        int json_len = c->res[3] | (c->res[4] << 8) | (c->res[5] << 16);
        int fd, ret;

        if (c->res[2] == 2) {
            json = os_kmalloc(json_len, os_gfp_kernel);
            if (json == NULL) {
                os_printk("os_kmalloc json mem failed!\n");
                return -EI_ERR_ISP_NOMEM;
            }
            os_memcpy((void *)(json + c->addr), data, c->len);
            return EI_SUCCESS;
        } else if (c->res[2] == 1) {
            os_memcpy((void *)(json + c->addr), data, c->len);
            return EI_SUCCESS;
        } else if (c->res[2] == 0) {
            os_memcpy((void *)(json + c->addr), data, c->len);

            ISP_TOOL_GetJsonName(dev, json_name);

            os_sprintf(file_path, "%s/%s.json", ISP_TOOL_GetPathName(dev), json_name);
            fd = open(file_path, O_RDWR | O_CREAT | O_TRUNC, 0644);
            if (fd < 0) {
                os_printk("open %s failed!\n", file_path);
                return EI_FAILURE;
            }
            ret = write(fd, json, json_len);
            if (ret < 0) {
                os_printk("write %s error!\n", file_path);
                close(fd);
                return ret;
            }
            close(fd);
            os_kfree(json);
            json = NULL;
        }
    } else if (c->res[1] == ISP_USB_CMD_SET_SENSOR_REG) {
        os_printk("ISP_USB_CMD_SET_SENSOR_REG\n");
        EI_U32 *reg_value = data;
        ISP_TOOL_SensorWrite(dev, c->addr, *reg_value);
    }

    if ((c->res[1] == ISP_USB_CMD_SET_RELOAD) || (c->res[1] == ISP_USB_CMD_SET_JSON_RELOAD)) {
        ISP_TOOL_ReLoadJson(dev);
    }

    return EI_SUCCESS;
}
RTM_EXPORT(isp_usb_write);

int isp_driver_init(void)
{
    int ret = 0;
    int id = 0;

    g_astIspRes[id].u32IspDev = id;
    g_astIspRes[id].pIspBase = (EI_VOID *)(EI_UL)(0x014c0000);

    g_astIspRes[id].s32IspCoreIrq = INT_ISP_CORE;
    ret = os_request_irq(g_astIspRes[id].s32IspCoreIrq, isp_core_irq_handler, NULL, "IspCore", &g_astIspRes[id]);
    if (ret < 0) {
        os_printk("irq%d request failed\n", g_astIspRes[id].s32IspCoreIrq);
        return EI_FAILURE;
    }

    g_astIspRes[id].s32IppuChnIrq = INT_ISP_IPPU;
    ret = os_request_irq(g_astIspRes[id].s32IppuChnIrq, ippu_chn_irq_handler, NULL, "IppuChn", &g_astIspRes[id]);
    if (ret < 0) {
        os_printk("irq%d request failed\n", g_astIspRes[id].s32IppuChnIrq);
        return EI_FAILURE;
    }

    g_astIspRes[id].s32IspIprocIrq = INT_ISP_IPROC;
    ret = os_request_irq(g_astIspRes[id].s32IspIprocIrq, isp_iproc_irq_handler, NULL, "IspIproc", &g_astIspRes[id]);
    if (ret < 0) {
        os_printk("irq%d request failed\n", g_astIspRes[id].s32IspIprocIrq);
        return EI_FAILURE;
    }

    g_astIspRes[id].u32IspClk = 160000000;
    g_astIspRes[id].u32IppuClk = 160000000;

    ISP_ModInit();

    os_printk("%s\n", "isp init ok");

    return 0;
}

#else

static int ei_isp_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    int ret = 0;
    int id = 0;
    struct os_timeval t0, t1, t2;
    EI_U64 time1, time2;

    os_gettimeofday(&t0);

    g_astIspRes[id].u32IspDev = id;
    g_astIspRes[id].pIspBase = of_iomap(dev->of_node, 0);
    if (!g_astIspRes[id].pIspBase) {
        os_printk("map isp%d reg failed!\n", id);
        return EI_FAILURE;
    }

    ISP_ModInit();

    os_gettimeofday(&t1);

    g_astIspRes[id].s32IspCoreIrq = irq_of_parse_and_map(dev->of_node, 0);
    if (g_astIspRes[id].s32IspCoreIrq < 0) {
        os_printk("isp%d no core irq found\n", id);
        return g_astIspRes[id].s32IspCoreIrq;
    }

    ret = os_request_irq(g_astIspRes[id].s32IspCoreIrq, isp_core_irq_handler, NULL, dev_name(dev), &g_astIspRes[id]);
    if (ret < 0) {
        os_printk("irq%d request failed\n", g_astIspRes[id].s32IspCoreIrq);
        return EI_FAILURE;
    }

    g_astIspRes[id].s32IppuChnIrq = irq_of_parse_and_map(dev->of_node, 1);
    if (g_astIspRes[id].s32IppuChnIrq < 0) {
        os_printk("isp%d no chn irq found\n", id);
        return g_astIspRes[id].s32IppuChnIrq;
    }

    ret = os_request_irq(g_astIspRes[id].s32IppuChnIrq, ippu_chn_irq_handler, NULL, dev_name(dev), &g_astIspRes[id]);
    if (ret < 0) {
        os_printk("irq%d request failed\n", g_astIspRes[id].s32IppuChnIrq);
        return EI_FAILURE;
    }

    g_astIspRes[id].s32IspIprocIrq = irq_of_parse_and_map(dev->of_node, 2);
    if (g_astIspRes[id].s32IspIprocIrq < 0) {
        os_printk("isp%d no iproc irq found\n", id);
        return g_astIspRes[id].s32IspIprocIrq;
    }

    ret = os_request_irq(g_astIspRes[id].s32IspIprocIrq, isp_iproc_irq_handler, NULL, dev_name(dev), &g_astIspRes[id]);
    if (ret < 0) {
        os_printk("irq%d request failed\n", g_astIspRes[id].s32IspIprocIrq);
        return EI_FAILURE;
    }

    ret = of_property_read_u32(dev->of_node, "isp_clock", &g_astIspRes[id].u32IspClk);
    if (ret < 0) {
        os_printk("isp%d get clk freq failed\n", id);
        g_astIspRes[id].u32IspClk = 198000000;
    }

    ret = of_property_read_u32(dev->of_node, "ippu_clock", &g_astIspRes[id].u32IppuClk);
    if (ret < 0) {
        os_printk("ippu%d get clk freq failed\n", id);
        g_astIspRes[id].u32IppuClk = 198000000;
    }

    platform_set_drvdata(pdev, &g_astIspRes[id]);

    os_gettimeofday(&t2);

    time1 = t1.tv_sec * 1000000 + t1.tv_usec - (t0.tv_sec * 1000000 + t0.tv_usec);
    time2 = t2.tv_sec * 1000000 + t2.tv_usec - (t0.tv_sec * 1000000 + t0.tv_usec);

    os_printk("isp init ok, cost time %lld(us) %lld(us)\n", time1, time2);

    return 0;
}

static int ei_isp_remove(struct platform_device *pdev)
{
    ISP_RESOURCE_S *pstIspRes = platform_get_drvdata(pdev);

    ISP_ModExit();

    os_free_irq(pstIspRes->s32IspCoreIrq, pstIspRes);
    os_free_irq(pstIspRes->s32IppuChnIrq, pstIspRes);
    os_free_irq(pstIspRes->s32IspIprocIrq, pstIspRes);

    os_printk("isp exit ok\n");

    return 0;
}

#ifdef CONFIG_PM_GENERIC_DOMAINS
static int ei_isp_runtime_suspend(struct device *dev)
{
    /* TODO */

    return 0;
}

static int ei_isp_runtime_resume(struct device *dev)
{
    /* TODO */

    return 0;
}
#endif

#ifdef CONFIG_PM_SLEEP
static int ei_isp_suspend(struct device *dev)
{
#ifndef CONFIG_PM_GENERIC_DOMAINS
    /* TODO */
#endif

    return 0;
}

static int ei_isp_resume(struct device *dev)
{
#ifndef CONFIG_PM_GENERIC_DOMAINS
    /* TODO */
#endif

    return 0;
}
#endif

#ifdef CONFIG_PM
static const struct dev_pm_ops ei_isp_pm_ops = {
#ifdef CONFIG_PM_GENERIC_DOMAINS
    SET_RUNTIME_PM_OPS(ei_isp_runtime_suspend, ei_isp_runtime_resume, NULL)
#endif

#ifdef CONFIG_PM_SLEEP
    SET_SYSTEM_SLEEP_PM_OPS(ei_isp_suspend, ei_isp_resume)
#endif
};
#endif

static const struct of_device_id ei_isp_match[] = {
    { .compatible = "ei, ei-isp" },
    {},
};

MODULE_DEVICE_TABLE(of, ei_isp_match);

static struct platform_driver ei_isp_driver = {
    .probe          = ei_isp_probe,
    .remove         = ei_isp_remove,
    .driver         = {
        .name           = "ei_isp",
        .of_match_table = ei_isp_match,
#ifdef CONFIG_PM
        .pm             = &ei_isp_pm_ops,
#endif
    },
};

os_module_platform_driver(ei_isp_driver);

MODULE_LICENSE("GPL");

#endif

